package com.pinfly.common.config;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;

import com.pinfly.common.util.StringUtil;

/**
 * This class extends the Spring property place holder configurer to implement
 * functionality to support encrypted property values and also value
 * substitution. For example if the parameters value contains ${name}, it will
 * try and look for a parameter called "name" and use its value for the
 * parameter. This lookup is only supported one-level deep, it does not support
 * nested values. Also, if the value starts with the "!encrypted!" keyword the
 * rest of the value is considered to be an encrypted value, there cannot be any
 * additional prefixes "${name}" or more than one "!encrypted!" in a value that
 * starts with "!encrypted!".
 */
public class PfyPropertiesPlaceholderConfigurer extends PropertyPlaceholderConfigurer
{
    private static final Logger logger = Logger.getLogger (PfyPropertiesPlaceholderConfigurer.class);

    // The following keys are for value substitution
    // if you have the following
    // default.parameter=1234
    // myparameter=${default.parameter}
    // The actual value for myparameter would be 1234
    private static final String PREFIX_KEY = "${";
    private static final String SUFFIX_KEY = "}";

    private String[] groupNames;

    /**
     * Constructor, creates an instance of the crypto factory implementation.
     */
    public PfyPropertiesPlaceholderConfigurer ()
    {
    }

    @Override
    protected String resolvePlaceholder (String placeholder, Properties props)
    {
        String value = props.getProperty (placeholder);
        if (value != null)
        {
            String[] replacements = StringUtil.substringsBetween (value, PREFIX_KEY, SUFFIX_KEY);

            if (replacements != null && replacements.length > 0)
            {
                for (String substitute : replacements)
                {
                    String newValue = props.getProperty (substitute);
                    if (newValue != null)
                    {
                        // Replace the value if the new value was found or it
                        // was decrypted, otherwise
                        // just use the original value.
                        value = StringUtil.replace (value, PREFIX_KEY + substitute + SUFFIX_KEY, newValue);
                    }
                }
            }
        }

        return value;
    }

    /**
     * Sets the group name or property file name.
     * 
     * @param group
     */
    public void setGroupName (String group)
    {
        groupNames = new String[]
        { group };
    }

    /**
     * Set the groups names which are the property file names.
     * 
     * @param groups
     */
    public void setGroupNames (String[] groups)
    {
        this.groupNames = groups;
    }

    @Override
    protected void loadProperties (Properties props) throws IOException
    {
        if (groupNames != null)
        {
            // we load each property file individually, even though
            // CfgPropertiesFactory could load them all at once
            for (String group : groupNames)
            {
                try
                {
                    PfyProperties cfg = PfyPropertiesFactory.getProperties (group);

                    for (Map.Entry <Object, Object> entry : cfg.entrySet ())
                    {
                        // values are already decrypted
                        props.setProperty (entry.getKey ().toString (), entry.getValue ().toString ());
                    }
                }
                catch (ConfigException e)
                {
                    logger.warn ("Exception loading properties for group(s): " + groupNames, e);
                }
            }
        }
    }

    /**
     * This is used for testing purposes.
     * 
     * @param placeholder
     * @param props
     * @return the property value
     */
    public String getProperty (String placeholder, Properties props)
    {
        return this.resolvePlaceholder (placeholder, props);
    }

    @Override
    public void setLocation (Resource location)
    {
        throw new UnsupportedOperationException ("Must specify groupName(s) instead of location");
    }

    @Override
    public void setLocations (Resource[] locations)
    {
        throw new UnsupportedOperationException ("Must specify groupName(s) instead of location");
    }
}
